package com.xiaomaoguai.fcp.pre.kepler.router.rpc.server.servlet.response;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import com.google.common.base.Optional;
import com.google.common.net.MediaType;
import com.xiaomaoguai.fcp.pre.kepler.router.rpc.constants.HttpConstants;
import com.xiaomaoguai.fcp.pre.kepler.router.rpc.server.servlet.request.NettyHttpServletRequest;

import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.util.AsciiString;
import io.netty.util.concurrent.FastThreadLocal;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;

import static com.google.common.base.Preconditions.checkState;

/**
 * Http响应对象
 */
public class NettyHttpServletResponse implements HttpServletResponse {

	public static final String SESSION_REQUEST_PARAMETER_NAME = "ultronSessionid";

	/**
	 * SimpleDateFormat非线程安全，为了节省内存提高效率，把他放在ThreadLocal里
	 * 用于设置HTTP响应头的时间信息
	 */
	private static final FastThreadLocal<DateFormat> FORMAT = new FastThreadLocal<DateFormat>() {

		@Override
		protected DateFormat initialValue() {
			DateFormat df = new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss z", Locale.ENGLISH);
			df.setTimeZone(TimeZone.getTimeZone("GMT"));
			return df;
		}
	};

	private static final Locale DEFAULT_LOCALE = Locale.getDefault();

	private static final String DEFAULT_CHARACTER_ENCODING = Charsets.UTF_8.name();

	private NettyHttpServletRequest request;

	private FullHttpResponse response;

	private NettyHttpServletOutputStream outputStream;

	private boolean usingOutputStream;

	private PrintWriter writer;

	private boolean committed;

	private List<Cookie> cookies;

	private String contentType;

	private String characterEncoding = DEFAULT_CHARACTER_ENCODING;

	private Locale locale;

	/**
	 * 构造方法
	 *
	 * @param ctx            Netty的Context
	 * @param servletContext ServletContext
	 * @param response       Netty自带的http响应对象，初始化为200
	 */
	public NettyHttpServletResponse(NettyHttpServletRequest request, DefaultFullHttpResponse response) {
		this.request = request;
		this.response = response;
		this.outputStream = new NettyHttpServletOutputStream(response);
		cookies = new ArrayList<>();
	}

	public ChannelHandlerContext getCtx() {
		return request.getCtx();
	}

	/**
	 * 设置基本的请求头
	 */
	public FullHttpResponse getNettyResponse() {
		if (committed) {
			return response;
		}
		committed = true;
		HttpHeaders headers = response.headers();
		if (null != contentType) {
			String value = null == characterEncoding ? contentType : contentType + "; charset=" + characterEncoding; //Content Type 响应头的内容
			headers.set(HttpHeaderNames.CONTENT_TYPE, value);
		}
		CharSequence date = getFormattedDate();
		headers.set(HttpHeaderNames.DATE, date); // 时间日期响应头
		headers.set(HttpHeaderNames.SERVER, HttpConstants.HEADER_SERVER); //服务器信息响应头
		// 跨域
		headers.set("Access-Control-Allow-Origin", "*");
		headers.set("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
		headers.set("Access-Control-Allow-Headers", "Content-Type");
		headers.set("Access-Control-Max-Age", "1800");// 30 min
		return response;
	}

	public void setNettyResponse(FullHttpResponse response) {
		this.response = response;
	}

	/**
	 * @return 线程安全的获取当前时间格式化后的字符串
	 */
	@VisibleForTesting
	private CharSequence getFormattedDate() {
		return new AsciiString(FORMAT.get().format(new Date()));
	}

	@Override
	public void addCookie(Cookie cookie) {
		cookies.add(cookie);
	}

	@Override
	public boolean containsHeader(String name) {
		return response.headers().contains(name);
	}

	@Override
	public String encodeURL(String url) {
		if (!request.isRequestedSessionIdFromCookie()) {
			//来自Cookie的Session ID,则客户端肯定支持Cookie，无需重写URL
			return url;
		}
		return url + ";" + SESSION_REQUEST_PARAMETER_NAME + "=" + request.getRequestedSessionId();
	}

	@Override
	public String encodeRedirectURL(String url) {
		return encodeURL(url);
	}

	@Override
	@Deprecated
	public String encodeUrl(String url) {
		return encodeURL(url);
	}

	@Override
	@Deprecated
	public String encodeRedirectUrl(String url) {
		return encodeRedirectURL(url);
	}

	@Override
	public void sendError(int sc, String msg) throws IOException {
		checkNotCommitted();
		response.setStatus(new HttpResponseStatus(sc, msg));
	}

	@Override
	public void sendError(int sc) throws IOException {
		checkNotCommitted();
		response.setStatus(HttpResponseStatus.valueOf(sc));
	}

	@Override
	public void sendRedirect(String location) throws IOException {
		checkNotCommitted();
		response.setStatus(HttpResponseStatus.FOUND);
		response.headers().set("Location", location);
	}

	@Override
	public void setDateHeader(String name, long date) {
		response.headers().set(name, date);
	}

	@Override
	public void addDateHeader(String name, long date) {
		response.headers().add(name, date);
	}

	@Override
	public void setHeader(String name, String value) {
		if (name == null || name.length() == 0 || value == null) {
			return;
		}
		if (isCommitted()) {
			return;
		}
		if (setHeaderField(name, value)) {
			return;
		}
		response.headers().set(name, value);
	}

	private boolean setHeaderField(String name, String value) {
		char c = name.charAt(0);//减少判断的时间，提高效率
		if ('C' == c || 'c' == c) {
			if (HttpHeaderNames.CONTENT_TYPE.contentEqualsIgnoreCase(name)) {
				setContentType(value);
				return true;
			}
		}
		return false;
	}

	private boolean hasWriter() {
		return null != writer;
	}

	@Override
	public void addHeader(String name, String value) {
		if (name == null || name.length() == 0 || value == null) {
			return;
		}
		if (isCommitted()) {
			return;
		}
		if (setHeaderField(name, value)) {
			return;
		}
		response.headers().add(name, value);
	}

	@Override
	public void setIntHeader(String name, int value) {
		if (name == null || name.length() == 0) {
			return;
		}
		if (isCommitted()) {
			return;
		}
		response.headers().set(name, value);
	}

	@Override
	public void addIntHeader(String name, int value) {
		if (name == null || name.length() == 0) {
			return;
		}
		if (isCommitted()) {
			return;
		}
		response.headers().add(name, value);
	}

	private void checkNotCommitted() {
		checkState(!committed, "Cannot perform this operation after response has been committed");
	}

	@Override
	public String getCharacterEncoding() {
		return characterEncoding;
	}

	@Override
	public String getContentType() {
		return contentType;
	}	@Override
	public void setStatus(int sc) {
		response.setStatus(HttpResponseStatus.valueOf(sc));
	}

	@Override
	public void setContentType(String type) {
		if (isCommitted()) {
			return;
		}
		if (hasWriter()) {
			return;
		}
		if (null == type) {
			contentType = null;
			return;
		}
		MediaType mediaType = MediaType.parse(type);
		Optional<Charset> charset = mediaType.charset();
		if (charset.isPresent()) {
			setCharacterEncoding(charset.get().name());
		}
		contentType = mediaType.type() + '/' + mediaType.subtype();
	}

	@Override
	public ServletOutputStream getOutputStream() throws IOException {
		checkState(!hasWriter(), "getWriter has already been called for this response");
		usingOutputStream = true;
		return outputStream;
	}	@Override
	@Deprecated
	public void setStatus(int sc, String sm) {
		response.setStatus(new HttpResponseStatus(sc, sm));
	}

	@Override
	public PrintWriter getWriter() throws IOException {
		checkState(!usingOutputStream, "getOutputStream has already been called for this response");
		if (!hasWriter()) {
			writer = new PrintWriter(outputStream);
		}
		return writer;
	}

	@Override
	public void setCharacterEncoding(String charset) {
		if (hasWriter()) {
			return;
		}
		characterEncoding = charset;
	}	@Override
	public int getStatus() {
		return response.status().code();
	}

	@Override
	public void setContentLength(int len) {
		HttpUtil.setContentLength(response, len);
	}

	@Override
	public void setContentLengthLong(long len) {
		HttpUtil.setContentLength(response, len);
	}	@Override
	public String getHeader(String name) {
		return response.headers().get(name);
	}



	@Override
	public Collection<String> getHeaders(String name) {
		return response.headers().getAll(name);
	}



	@Override
	public Collection<String> getHeaderNames() {
		return response.headers().names();
	}



	//Writer和OutputStream不能同时使用




	@Override
	public void setBufferSize(int size) {
	}

	@Override
	public int getBufferSize() {
		return outputStream.getBufferSize();
	}

	@Override
	public void flushBuffer() throws IOException {
		checkNotCommitted();
		outputStream.flush();
	}

	@Override
	public void resetBuffer() {
		checkNotCommitted();
		outputStream.resetBuffer();
	}

	@Override
	public boolean isCommitted() {
		return committed;
	}


	@Override
	public void reset() {
		resetBuffer();
		usingOutputStream = false;
		writer = null;
	}

	@Override
	public void setLocale(Locale loc) {
		locale = loc;
	}

	@Override
	public Locale getLocale() {
		return null == locale ? DEFAULT_LOCALE : locale;
	}
}
